﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Drawing;
using System.Drawing.Imaging;
using System.Windows.Forms;

namespace GraphicsEditor
{
    class ConvolutionMatrix
    {
        public int[,] matrix = { 
                                   { 0, 0, 0, 0, 0 }, 
                                   { 0, 0, 0, 0, 0 }, 
                                   { 0, 0, 1, 0, 0 }, 
                                   { 0, 0, 0, 0, 0 }, 
                                   { 0, 0, 0, 0, 0 } 
                               };
        /*
        public int topLeft = 0;
        public int topMiddle = 0;
        public int topRight = 0;
        public int middleLeft = 0;
        public int middle = 1; // this is the main pixel, the rest are surrounding pixels
        public int middleRight = 0;
        public int lowerLeft = 0;
        public int lowerMiddle = 0;
        public int lowerRight = 0;*/
        public int factor = 1;
        public int offset = 0;
        public void setAll(int toSet)
        {
            for (int i = 0; i < matrix.GetLength(0); i++)
            {
                for (int j = 0; j < matrix.GetLength(1); j++)
                {
                    matrix[i, j] = toSet;
                }
            }
        }

        public static Bitmap conv3x3(Bitmap source, ConvolutionMatrix matrix, bool wrap)
        {
            // Avoid divide by zero errors

            if (0 == matrix.factor)
                return source;

            Bitmap toChange = new Bitmap(source);
            BitmapData toChangeData = toChange.LockBits(new Rectangle(0, 0, toChange.Width, toChange.Height),
                                ImageLockMode.ReadWrite,
                                PixelFormat.Format32bppArgb);//.Format24bppRgb);
            BitmapData sourceData = source.LockBits(new Rectangle(0, 0, source.Width, source.Height),
                               ImageLockMode.ReadWrite,
                               PixelFormat.Format32bppArgb);
            int stride = toChangeData.Stride;
            int stride2 = stride * 2;

            System.IntPtr Scan0 = toChangeData.Scan0;
            System.IntPtr SrcScan0 = sourceData.Scan0;

            unsafe
            {
                byte* pixel = (byte*)(void*)Scan0;
                byte* pixelSource = (byte*)(void*)SrcScan0;
                int nOffset = stride - toChange.Width * 4;
                int nWidth = toChange.Width;
                int nHeight = toChange.Height;

                for (int y = 0; y < nHeight; y++)
                {
                    for (int x = 0; x < nWidth; x++)
                    {
                        int yMiddle = y * stride;
                        int xMiddle = x * 4;
                        int yOuterTop = (y - 2) * stride;
                        if (yOuterTop < 0)
                        {
                            if (wrap)
                                yOuterTop = (nHeight + yOuterTop) * stride;
                            else
                                yOuterTop = yMiddle;
                        }
                        int yTop = (y - 1) * stride;
                        if (yTop < 0)
                        {
                            if (wrap)
                                yTop = (nHeight + yTop) * stride;
                            else
                                yTop = yMiddle;

                        }

                        int yOuterBottom = (y + 2) * stride;
                        if (yOuterBottom >= nHeight * stride)
                        {
                            if (wrap)
                                yOuterBottom = ( yOuterBottom - nHeight) * stride;
                            else
                                yOuterBottom = yMiddle;

                        }

                        int yBottom = (y + 1) * stride;
                        if (yBottom >= nHeight * stride)
                        {
                            if (wrap)
                                yBottom = (nHeight - yBottom) * stride;
                            else
                                yBottom = yMiddle;

                        }

                        int xOuterLeft = (x - 2) * 4;
                        if (xOuterLeft < 0)
                        {
                            if (wrap)
                                xOuterLeft = (nWidth + xOuterLeft) * 4;
                            else
                                xOuterLeft = xMiddle;

                        }

                        int xLeft = (x - 1) * 4;
                        if (xLeft < 0)
                        {
                            if (wrap)
                                xLeft = (nWidth + xLeft) * 4;
                            else
                                xLeft = xMiddle;
                        }

                        int xOuterRight = (x + 2) * 4;
                        if (xOuterRight >= nWidth * 4)
                        {
                            if (wrap)
                                xOuterRight = (xOuterLeft - nWidth) * 4;
                            else
                                xOuterRight = xMiddle;

                        }

                        int xRight = (x + 1) * 4;
                        if (xRight >= nWidth * 4)
                        {
                            if (wrap)
                                xRight = (xRight - nWidth) * 4;
                            else
                                xRight = xMiddle;
                        }

//                        Color currentColor = Color.FromArgb(pixelSource[yMiddle + xMiddle + 3],
//                            pixelSource[yMiddle + xMiddle + 2], pixelSource[yMiddle + xMiddle + 1], pixelSource[yMiddle + xMiddle]);

                        int[,] locations = { 
                                               { yOuterTop + xOuterLeft, yOuterTop + xLeft, yOuterTop + xMiddle, yOuterTop + xRight, yOuterTop + xOuterRight },
                                               { yTop + xOuterLeft, yTop + xLeft, yTop + xMiddle, yTop + xRight, yTop + xOuterRight },
                                               { yMiddle + xOuterLeft, yMiddle + xLeft, yMiddle + xMiddle, yMiddle + xRight, yMiddle + xOuterRight },
                                               { yBottom + xOuterLeft, yBottom + xLeft, yBottom + xMiddle, yBottom + xRight, yBottom + xOuterRight },
                                               { yOuterBottom + xOuterLeft, yOuterBottom + xLeft, yOuterBottom + xMiddle, yOuterBottom + xRight, yOuterBottom + xOuterRight }
                                           };
                        int red = 0;
                        int blue = 0;
                        int green = 0;

                        for (int i = 0; i < 5; i++)
                        {
                            for (int j = 0; j < 5; j++)
                            {
                                red += matrix.matrix[i, j] * pixelSource[locations[i, j] + 2];
                                blue += matrix.matrix[i, j] * pixelSource[locations[i, j]];
                                green += matrix.matrix[i, j] * pixelSource[locations[i, j] + 1];
                            }
                        }
                        red /= matrix.factor;
                        red += matrix.offset;
                        if (red < 0) red = 0;
                        if (red > 255) red = 255;
                        pixel[yMiddle + xMiddle + 2] = (byte)red;
                        blue /= matrix.factor;
                        blue += matrix.offset;
                        if (blue < 0) blue = 0;
                        if (blue > 255) blue = 255;
                        pixel[yMiddle + xMiddle] = (byte)blue;
                        green /= matrix.factor;
                        green += matrix.offset;
                        if (green < 0) green = 0;
                        if (green > 255) green = 255;
                        pixel[yMiddle + xMiddle + 1] = (byte)green;
                    }
                }
            }

            toChange.UnlockBits(toChangeData);
            source.UnlockBits(sourceData);

            return toChange;
        }
        // proof of concept
        public static void addMenuItems(ToolStripMenuItem menu)
        {
            ToolStripMenuItem smooth = new ToolStripMenuItem();
            smooth.MergeAction = MergeAction.Replace;
            smooth.Name = "convolutionSmooth";
            smooth.Size = new Size(116, 22);
            smooth.Text = "Smooth";
            smooth.Click += new System.EventHandler(smoothClick);
            menu.DropDownItems.Add(smooth);

            ToolStripMenuItem sobel = new ToolStripMenuItem();
            smooth.MergeAction = MergeAction.Replace;
            smooth.Name = "convolutionSobel";
            smooth.Size = new Size(116, 22);
            smooth.Text = "Sobel";
            smooth.Click += new System.EventHandler(smoothClick);
            menu.DropDownItems.Add(smooth);
        }

        public static void smoothClick(object sender, EventArgs e)
        {
            ToolStripMenuItem item = (ToolStripMenuItem)sender;
            ToolStripItem tsi = item.OwnerItem;
            ToolStrip ts = tsi.GetCurrentParent();
            FormMainWindow mainForm = (FormMainWindow)ts.FindForm();
            ImageForm form = (ImageForm)mainForm.dockPanel.ActiveContent.DockHandler.Form;
            Undo temp = new Undo(new Bitmap(form.pictureWindow.Image));
            form.undoOptions.Push(temp);
            form.undoToolStripMenuItem.Enabled = true;
            Bitmap currentImage = new Bitmap(form.pictureWindow.Image);
            form.pictureWindow.Image = ConvolutionMatrix.smooth(currentImage, 1);
        }

        public static Bitmap smooth(Bitmap src, int weight)
        {
            ConvolutionMatrix matrix = new ConvolutionMatrix();
            matrix.setAll(1);
            matrix.matrix[2,2] = weight;
            matrix.factor = weight + 8;

            return ConvolutionMatrix.conv3x3(src, matrix, false);
        }

        public static Bitmap sobel(Bitmap src)
        {

            ConvolutionMatrix matrix = new ConvolutionMatrix();
            matrix.matrix = new int[,]  { 
                                            { 0,  0,  0,  0, 0 }, 
                                            { 0, -1, -1, -1, 0 }, 
                                            { 0, -1,  8, -1, 0 }, 
                                            { 0, -1, -1, -1, 0 }, 
                                            { 0,  0,  0,  0, 0 } 
                                        };
            //matrix.offset = 127;
            return ConvolutionMatrix.conv3x3(src, matrix, false);
        }
        public static Bitmap blur(Bitmap src)
        {
            ConvolutionMatrix matrix = new ConvolutionMatrix();
            matrix.matrix = new int[,] { 
                                            { 0, 0, 0, 0, 0 }, 
                                            { 0, 1, 2, 1, 0 }, 
                                            { 0, 2, 4, 2, 0 }, 
                                            { 0, 1, 2, 1, 0 }, 
                                            { 0, 0, 0, 0, 0 } 
                                        };
            matrix.factor = 16;
            return ConvolutionMatrix.conv3x3(src, matrix, false);
        }
        public static Bitmap sharpen(Bitmap src)
        {
            ConvolutionMatrix matrix = new ConvolutionMatrix();
            matrix.matrix = new int[,]  { 
                                            { 0,  0,  0,  0, 0 }, 
                                            { 0,  0, -1,  0, 0 }, 
                                            { 0, -1,  5, -1, 0 }, 
                                            { 0,  0, -1,  0, 0 }, 
                                            { 0,  0,  0,  0, 0 } 
                                        };
            return ConvolutionMatrix.conv3x3(src, matrix, false);
        }

        public static Bitmap edge_detect(Bitmap src)
        {
            ConvolutionMatrix matrix = new ConvolutionMatrix();
            matrix.matrix = new int[,] { 
                                            { 0, 0,  0, 0, 0 }, 
                                            { 0, 0,  1, 0, 0 }, 
                                            { 0, 1, -4, 1, 0 }, 
                                            { 0, 0,  1, 0, 0 }, 
                                            { 0, 0,  0, 0, 0 } 
                                        };
            return ConvolutionMatrix.conv3x3(src, matrix, false);
        }
        public static Bitmap edge_enhance(Bitmap src)
        {
            ConvolutionMatrix matrix = new ConvolutionMatrix();

            matrix.matrix[2,1] = -1;
            matrix.matrix[2,2] = 1;
            
            return ConvolutionMatrix.conv3x3(src, matrix, false);
        }
        public static Bitmap emboss(Bitmap src)
        {
            ConvolutionMatrix matrix = new ConvolutionMatrix();
            matrix.matrix = new int[,]  { 
                                            { 0,  0,  0,  0, 0 }, 
                                            { 0, -2, -1,  0, 0 }, 
                                            { 0, -1,  1,  1, 0 }, 
                                            { 0,  0,  1,  2, 0 }, 
                                            { 0,  0,  0,  0, 0 } 
                                        };

            return ConvolutionMatrix.conv3x3(src, matrix, false);
        }
        public static Bitmap mean_removal(Bitmap src)
        {
            ConvolutionMatrix matrix = new ConvolutionMatrix();
            matrix.matrix = new int[,]  { 
                                            { 0,  0,  0,  0, 0 }, 
                                            { 0, -1, -1, -1, 0 }, 
                                            { 0, -1,  9, -1, 0 }, 
                                            { 0, -1, -1, -1, 0 }, 
                                            { 0,  0,  0,  0, 0 } 
                                        };
            return ConvolutionMatrix.conv3x3(src, matrix, false);
        }
    }
}
